import re
import bcrypt
from datetime import datetime, timedelta
from typing import Optional, Dict, Any
from sqlalchemy.orm import Session
from jose import JWTError, jwt
from fastapi import HTTPException, status

from app.core.config import settings
from app.models.user import User
from app.schemas.auth import TokenData, UserAuthInfo
from app.services.user_service import UserService
from app.core.logging import get_logger

logger = get_logger(__name__)


class AuthService:
    """认证服务类"""
    
    # 密码策略配置
    PASSWORD_MIN_LENGTH = 8
    PASSWORD_REQUIRE_UPPERCASE = True
    PASSWORD_REQUIRE_LOWERCASE = True
    PASSWORD_REQUIRE_DIGITS = True
    PASSWORD_REQUIRE_SPECIAL_CHARS = True
    PASSWORD_SPECIAL_CHARS = "!@#$%^&*()_+-=[]{}|;:,.<>?"
    
    def __init__(self, db: Session):
        self.db = db
        self.user_service = UserService(db)
    
    def validate_password_policy(self, password: str) -> bool:
        """验证密码是否符合策略要求"""
        errors = []
        
        # 检查最小长度
        if len(password) < self.PASSWORD_MIN_LENGTH:
            errors.append(f"密码长度至少需要{self.PASSWORD_MIN_LENGTH}个字符")
        
        # 检查大写字母
        if self.PASSWORD_REQUIRE_UPPERCASE and not re.search(r'[A-Z]', password):
            errors.append("密码必须包含至少一个大写字母")
        
        # 检查小写字母
        if self.PASSWORD_REQUIRE_LOWERCASE and not re.search(r'[a-z]', password):
            errors.append("密码必须包含至少一个小写字母")
        
        # 检查数字
        if self.PASSWORD_REQUIRE_DIGITS and not re.search(r'\d', password):
            errors.append("密码必须包含至少一个数字")
        
        # 检查特殊字符
        if self.PASSWORD_REQUIRE_SPECIAL_CHARS:
            pattern = r'[' + re.escape(self.PASSWORD_SPECIAL_CHARS) + ']'
            if not re.search(pattern, password):
                errors.append(f"密码必须包含至少一个特殊字符: {self.PASSWORD_SPECIAL_CHARS}")
        
        # 检查常见弱密码
        weak_passwords = [
            'password', '12345678', 'qwerty', 'admin', 'welcome',
            'letmein', 'monkey', 'password1', '123456789'
        ]
        if password.lower() in weak_passwords:
            errors.append("密码过于简单，请使用更复杂的密码")
        
        # 检查是否包含用户名或邮箱（在注册时验证）
        if hasattr(self, '_check_username_in_password'):
            username = getattr(self, '_username', '')
            email = getattr(self, '_email', '')
            
            if username and username.lower() in password.lower():
                errors.append("密码不能包含用户名")
            
            if email and email.split('@')[0].lower() in password.lower():
                errors.append("密码不能包含邮箱前缀")
        
        if errors:
            error_message = "; ".join(errors)
            logger.warning(f"密码策略验证失败: {error_message}")
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=error_message
            )
        
        return True
    
    def get_password_hash_with_policy(self, password: str, username: str = "", email: str = "") -> str:
        """在密码哈希前验证密码策略"""
        # 设置用于检查的用户名和邮箱
        self._username = username
        self._email = email
        self._check_username_in_password = True
        
        # 验证密码策略
        self.validate_password_policy(password)
        
        # 生成密码哈希
        return self.get_password_hash(password)
    
    def verify_password(self, plain_password: str, hashed_password: str) -> bool:
        """验证密码"""
        try:
            # 直接使用bcrypt验证密码
            return bcrypt.checkpw(plain_password.encode('utf-8'), hashed_password.encode('utf-8'))
        except Exception:
            return False
    
    def get_password_hash(self, password: str) -> str:
        """获取密码哈希"""
        # 使用bcrypt直接生成哈希，自动处理密码长度限制
        hashed = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
        return hashed.decode('utf-8')
    
    def authenticate_user(self, email: str, password: str) -> Optional[User]:
        """验证用户登录"""
        user = self.user_service.get_by_email(email)
        if not user:
            return None
        if not user.is_active:
            return None
        if not self.verify_password(password, user.hashed_password):
            return None
        
        # 更新最后登录时间
        user.last_login_at = datetime.utcnow()
        self.db.commit()
        
        return user
    
    def create_access_token(self, data: Dict[str, Any], expires_delta: Optional[timedelta] = None) -> str:
        """创建访问令牌"""
        to_encode = data.copy()
        if expires_delta:
            expire = datetime.utcnow() + expires_delta
        else:
            expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
        
        to_encode.update({"exp": expire})
        encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
        return encoded_jwt
    
    def create_refresh_token(self, user_id: int) -> str:
        """创建刷新令牌"""
        expire = datetime.utcnow() + timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
        to_encode = {
            "user_id": user_id,
            "exp": expire,
            "type": "refresh"
        }
        return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
    
    def verify_token(self, token: str) -> Optional[TokenData]:
        """验证令牌"""
        try:
            payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
            user_id: int = payload.get("user_id")
            if user_id is None:
                return None
            
            # 获取用户信息
            user = self.user_service.get_by_id(user_id)
            if not user or not user.is_active:
                return None
            
            # 获取用户权限
            permissions = user.get_all_permissions()
            role_names = [role.name for role in user.roles if role.is_active]
            
            return TokenData(
                user_id=user.id,
                username=user.username,
                email=user.email,
                roles=role_names,
                permissions=list(permissions),
                is_superuser=user.is_superuser
            )
        except JWTError:
            return None
    
    def refresh_access_token(self, refresh_token: str) -> Optional[str]:
        """刷新访问令牌"""
        try:
            payload = jwt.decode(refresh_token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
            if payload.get("type") != "refresh":
                return None
            
            user_id: int = payload.get("user_id")
            if user_id is None:
                return None
            
            user = self.user_service.get_by_id(user_id)
            if not user or not user.is_active:
                return None
            
            return self.create_access_token({"user_id": user.id})
        except JWTError:
            return None
    
    def change_password(self, user_id: int, current_password: str, new_password: str) -> bool:
        """修改密码"""
        user = self.user_service.get_by_id(user_id)
        if not user:
            return False
        
        if not self.verify_password(current_password, user.hashed_password):
            return False
        
        user.hashed_password = self.get_password_hash(new_password)
        self.db.commit()
        return True
    
    def get_current_user_info(self, user_id: int) -> Optional[UserAuthInfo]:
        """获取当前用户信息"""
        user = self.user_service.get_by_id(user_id)
        if not user:
            return None
        
        permissions = user.get_all_permissions()
        role_names = [role.name for role in user.roles if role.is_active]
        
        return UserAuthInfo(
            user_id=user.id,
            username=user.username,
            email=user.email,
            roles=role_names,
            permissions=list(permissions),
            is_superuser=user.is_superuser,
            is_active=user.is_active,
            last_login_at=user.last_login_at.isoformat() if user.last_login_at else None
        )